package pCloud.obligation;

import java.io.OutputStream;
import java.net.URI;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import pCloud.PCloudAttributeAndType;
import pCloud.ResourceConnection;
import pCloud.Utilities;
import pCloud.message.DatabaseReply;
import pCloud.message.DatabaseRequest;
import pCloud.server.DatabaseHandle;

import com.sun.xacml.Obligation;
import com.sun.xacml.attr.AttributeValue;
import com.sun.xacml.attr.DoubleAttribute;
import com.sun.xacml.attr.StringAttribute;
import com.sun.xacml.ctx.Attribute;
import com.sun.xacml.ctx.RequestCtx;

/**
 * This class handles the policies in which data is only given when the values
 * supplied in the request are close to the real data.
 * 
 * Assumptions: the distance metric between 2 sets of values (x1,..,xn) and
 * (y1,..,yn) is sqrt((x1-y1)^2 + ... + (xn-yn)^2)
 * 
 * The values included in the request are of the form: <columnId>:<value>
 * 
 */
public class ApproximationObligation extends DataProcessingObligation {

	public Object processObligation(String joiningColumn, DatabaseHandle rc,
			HashMap<URI, Obligation> obligationMap, RequestCtx requestCtx)
			throws Exception {

		// 1. First, construct a select clause
		String selectClause = this.constructSelectClause(obligationMap,
				requestCtx);

		// 2. Get the map of all the request values: <columnId>:<value>
		Set resourceAtts = requestCtx.getResource();
		ArrayList<AttributeValue> valueList = Utilities.getAttributeValue(
				PCloudAttributeAndType.RESOURCE_DATA_VALUE, resourceAtts);
		HashMap<String, Double> valueMap = this.getRequestValues(valueList);

		// 3. Next, get the list of all columns included in the Obligation. This
		// list must be an
		// exact match of all the keys in the valueMap
		Obligation obligation = obligationMap.get(new URI(
				PCloudAttributeAndType.OBLIGATION_APPROXIMATION_PARAM));
		List obligationValueList = obligation.getAssignments();

		Obligation obligationDataVal = obligationMap.get(new URI(
				PCloudAttributeAndType.OBLIGATION_APPROXIMATION_VALUE));
		if (obligationDataVal == null)
			throw new Exception(
					"Distance value required for a approximation policy");

		Attribute att = (Attribute) obligationDataVal.getAssignments().get(0);

		double distanceVal = ((DoubleAttribute) att.getValue()).getValue();

		Set<String> obligationValues = new HashSet<String>();
		String obligationColumnId;

		for (int i = 0; i < obligationValueList.size(); i++) {
			obligationColumnId = ((StringAttribute) ((Attribute) obligationValueList
					.get(i)).getValue()).getValue();
			if (!obligationValues.contains(obligationColumnId))
				obligationValues.add(obligationColumnId);
		}

		// 3.1. Check that the lists have same size
		if (obligationValueList.size() != valueMap.size()
				|| obligationValues.size() != obligationValueList.size()) {
			System.out.println(
					"data given in the request does not match that in the obligation");
			return null;
		} else { // 3.2. make sure every column ID is the same
			Iterator<String> it = obligationValues.iterator();
			String whereClause = "";
			String columnId;
			String distanceExpression = "";
			while (it.hasNext()) {
				columnId = it.next();
				if (!valueMap.containsKey(columnId)){
					System.out.println(
							"data given in the request is from the different column to what included in the obligation");
					return null;
				}
				distanceExpression = distanceExpression + "pow((" + columnId
						+ "-" + valueMap.get(columnId).doubleValue() + "),2)";
				if (it.hasNext())
					distanceExpression = distanceExpression + "+";
			}
			distanceExpression = " where sqrt(" + distanceExpression + ") < "
					+ distanceVal;

			// Construct a Database request
			DatabaseRequest dbr = new DatabaseRequest(selectClause
					+ this.getTableId(requestCtx) + distanceExpression);
			DatabaseReply results = rc.evaluateRequest(joiningColumn, dbr);

			// ResultSet results = (ResultSet)rc.executeQuery(selectClause +
			// tableId + distanceExpression);
			return results;
		}

	}

	private HashMap<String, Double> getRequestValues(
			ArrayList<AttributeValue> list) throws Exception {
		String value;
		String[] data;
		HashMap<String, Double> valueMap = new HashMap<String, Double>();

		for (int i = 0; i < list.size(); i++) {
			value = ((StringAttribute) list.get(i)).getValue();
			data = value.split(":");
			if (data.length != 2)
				throw new Exception(
						"Resource data is not well-formed: <columnId>:<value>");
			if (valueMap.containsKey(data[0]))
				throw new Exception(
						"Cannot supply 2 values for a single column in the request ");

			valueMap.put(data[0], (new Double(data[1])).doubleValue());
		}
		return valueMap;
	}
}
